#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>

#include "MemoryStack.h"

MemoryStack::MemoryStack(size_t size, size_t _alignment)
{
	alignment = getAlignedSize(_alignment, MEM_ALIGN); //align alignment to MEM_ALIGN to make sure everything is also memory aligned
	stackSize = getAlignedSize(size, alignment); //align total size to make sure there is no incomplete block
	
	
	printf("Requested stack with size %d and alignment %d. Memory aligned to %d bytes\n", size, _alignment, MEM_ALIGN);
	printf("Actual Size %d with actual alignment %d\n", stackSize, alignment);


	origin = malloc(stackSize); //allocate memory for stack
	if(origin==NULL) {
		error("memory allocation for stack failed",-1);
	}
	topPtr = origin; //topPtr of the stack is at the bottom, nothing pushed yet
}

MemoryStack::~MemoryStack(void)
{
	free(origin);
}

void* MemoryStack::push(size_t size)
{
	printf("push %d bytes; ",size);
	
	size = getAlignedSize(size, alignment); //add padding bytes for correct alignment

	printf("+padding = %d bytes\n",size);

	void* out = topPtr; //pointer to new element
	topPtr = (char*)topPtr+size; //topPtr is increment by the size of the new element

	//check for stack overflow
	if(topPtr > (char*)origin+stackSize) {
		error("STACK OVERFLOW",-1);
	}

	elementSizes.push(size); //add the size of the element we just pushed onto the stack

	//check if pointer is still aligned to MEM_ALIGN, with offset
	if((int)topPtr % MEM_ALIGN == 0) {
		printf("Still aligned\n");
	} else {
		printf("Pointer NOT Aligned, offset %d bytes\n", (int)topPtr%4);
	}

	return out;
}

void MemoryStack::pop()
{
	size_t size = elementSizes.top(); //get the size of the last element on the stack

	printf("pop %d bytes; ",size);
	size = getAlignedSize(size, alignment); //add padding bytes for correct alignment
	printf("+padding = %d bytes\n",size);
	
	//decrement topPtr by the size of the element to be popped from the stack
	topPtr = (char*)topPtr-size;

	//check for stack underflow
	if(topPtr < origin) {
		error("STACK UNDERFLOW",-1);
	}
	
	elementSizes.pop(); //remove the size of the element we just popped from the size stack
}

void* MemoryStack::top()
{
	size_t size = elementSizes.top(); //get the size of the last element on the stack

	printf("topPtr %d bytes; ",size);
	size = getAlignedSize(size, alignment); //add padding bytes for correct alignment
	printf("+padding = %d bytes\n",size);
	
	//decrement topPtr by the size of the element to be popped from the stack
	void* topPtr_element = (char*)topPtr-size;

	//check for stack underflow
	if(topPtr_element < origin) {
		error("STACK UNDERFLOW",-1);
	}
	
	return topPtr_element;
}

//makes first parameter divisible by the second parameter by enlarging it.
size_t MemoryStack::getAlignedSize(size_t origSize, size_t align)
{
	if(origSize <= align) {
		return align;
	} else if(origSize % align == 0) {
		return origSize;
	}

	return origSize + align - origSize%align;
}

void MemoryStack::error(char* err, int exitCode)
{
	fprintf(stderr,"ERROR: ");
	fprintf(stderr,err);
	fprintf(stderr,"\n");
	exit(exitCode);
}